﻿
#region License
/*
Copyright (c) 2009, Open University of the Netherlands
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Open University of the Netherlands nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion

#region Changelog

//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy - who - description
//----------   ---   -------------------------------------------------------------------------------
//05-12-2009 - veg - Got the BB Export to show content (a xml value set the content to hidden ;-()
//----------   ---   -------------------------------------------------------------------------------
//TODO             - See MoodleExport.cs
//
//             veg - COURSE_TOC (res_coursetoc) is missing.
//                 - Folder still invisible.
//----------   ---   -------------------------------------------------------------------------------
//NOTES            - See MoodleExport.cs
//----------   ---   -------------------------------------------------------------------------------

#endregion Changelog

namespace EwbDesigner.NET
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Windows.Forms;
    using System.Xml;
    using Ionic.Zip;
    using System.Collections.Generic;

    /// <summary>
    /// This script parses the instance.xml saved by the main program and walks all nodes in it. 
    ///
    /// It uses reflection based on node names or ExportedAs attribute values to call methods in this script.
    /// The result is that the function performing the export does not have to be aware of what is inside the 
    /// remainder of this script and what it's exporting.
    /// 
    /// Before and after the node walking it calls two more (hardcoded) methods to create a prolugue and clean/fixup.
    /// </summary>
    class BlackbBoardExport
    {
        #region Constants

        private static string bbnsURI = @"http://www.blackboard.com/content-packaging/";

        /// <summary>
        /// 0 = id
        /// 
        /// </summary>
        static String bbpackagesig = @"5BCFE5216F07246237EC906249AA5838";
        static String bbpackageinfo = null;

        static String res_label = null;
        static String res_text = null;
        static String res_course = null;
        static String res_coursetoc = null;
        static String res_folder = null;

        #endregion Constants

        #region Fields

        /// <summary>
        /// Input Document.
        /// </summary>
        private static XmlDocument doc = new XmlDocument();

        private static String BaseDirectory = ".";
        private static String ModelName = "";
        private static String Model = "";

        private static Char[] ListSeparator = CultureInfo.CurrentCulture.TextInfo.ListSeparator.ToCharArray();

        /// <summary>
        /// Generates unique ID'key.
        /// </summary>
        private static Int32 idcounter = 1;
        private static Int32 bbidentifier = 1;

        //Lookup Item Id to Identifier in *.dat files.
        private static Dictionary<String, String> bbids = new Dictionary<String, String>();

        /// <summary>
        /// Time of export.
        /// </summary>
        private static DateTime now;

        /// <summary>
        /// Output Document.
        /// </summary>
        private static XmlDocument xml = null;

        #endregion Fields

        #region Methods

        /// <summary>
        /// Loads a String resource from the main executable, Also works from within compiled script.
        /// </summary>
        /// <param name="Resource">The resource to be loaded</param>
        /// <returns></returns>
        private static String LoadResource(String Resource)
        {
            String apppath = Application.ExecutablePath.Replace(".vshost", "");

            //Note: Be Aware that when debugging the Executable path is 'EwbDesigner.vshost.exe' instead of 'EwbDesigner.exe'
            Assembly asm = Assembly.LoadFrom(apppath);

            //Debugging code, dump all resources...
            //foreach (String res in asm.GetManifestResourceNames())
            //{
            //    Debug.Print(res);
            //}

            if (asm != null && asm.GetManifestResourceInfo(Resource) != null)
            {
                //Assembly.GetCallingAssembly().GetManifestResourceInfo(Resource) != null)
                Debug.Print("Loading: '{0}' from '{1}'", Resource, apppath);

                return new StreamReader(asm.GetManifestResourceStream(Resource)).ReadToEnd();
                //return new StreamReader(Assembly.GetEntryAssembly().GetManifestResourceStream(Resource)).ReadToEnd();
            }

            Debug.Print("Resource '{0}' not found in '{1}'", Resource, apppath);

            return null;
        }

        /// <summary>
        /// Main Export Routine.
        /// </summary>
        /// <param name="model">Filename of the model to export</param>
        /// <returns>The resulting Zip archives filename</returns>
        public static String Execute(String model)
        {
            Console.WriteLine("[Blackboard Export]");
            Console.WriteLine("Instance=" + model);

            //Create a new XmlDocument (this does not give problems when executing the assembly twice).
            xml = new XmlDocument();

            ResetVariables(model);

            //Delete Moodle.xml or we will have problems with the XmlTextWriter later on.
            if (File.Exists(Path.Combine(BaseDirectory, "imsmanifest.xml")))
            {
                File.Delete(Path.Combine(BaseDirectory, "imsmanifest.xml"));
            }
            ///Cleanup any old files left...
            String[] dats = Directory.GetFiles(Application.StartupPath, "res*.dat");
            foreach (String dat in dats)
            {
                if (File.Exists(dat))
                {
                    File.Delete(dat);
                }
            }

            //Script Resources...
            bbpackageinfo = LoadResource(@"EwbDesigner.Scripts.bb-package-info.txt");
            bbpackagesig = LoadResource(@"EwbDesigner.Scripts.bb-package-sig.txt");
            res_label = LoadResource(@"EwbDesigner.Scripts.res_label.xml");
            res_text = LoadResource(@"EwbDesigner.Scripts.res_text.xml");
            res_course = LoadResource(@"EwbDesigner.Scripts.res_course.xml");
            res_coursetoc = LoadResource(@"EwbDesigner.Scripts.res_coursetoc.xml");
            res_folder = LoadResource(@"EwbDesigner.Scripts.res_folder.xml");

            //File Resources...
            //bbpackageinfo = File.ReadAllText(@"Scripts\bb-package-sig.txt");
            //res_label = File.ReadAllText(@"Scripts\res_label.xml");
            //res_text = File.ReadAllText(@"Scripts\res_text.xml");

            doc.Load(model);

            xml.PreserveWhitespace = true;

            //Write Some files...
            File.WriteAllText(".bb-package-info", bbpackageinfo);
            File.WriteAllText(".bb-package-sig", bbpackagesig);

            //Create Xml Stub...
            CreateBlackBoardHeader();

            //Debug.WriteLine("[Nodes]");
            XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");

            Recurse(doc.DocumentElement, organization);

            //Debug.WriteLine(";End Nodes");

            CreateBlackBoardFooter();

            /*
             * Fails:
             * 
             * XmlDeclaration xmldecl = xml.CreateXmlDeclaration("1.0", null, null);
             * xmldecl.Encoding = "UTF-8";
             * xml.InsertBefore(xmldecl, xml.DocumentElement);
             */

            //Save indented XML with Linebreaks to make it more readable...
            using (XmlTextWriter writer = new XmlTextWriter("imsmanifest.xml", System.Text.Encoding.UTF8))
            {
                writer.Formatting = Formatting.Indented;

                //Insert a BlackBoard Compatible processing instruction (Uppercase UTF-8, no standalone attribute & double quotes).
                writer.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");

                //Save Xml...
                xml.Save(writer);

                writer.Close();
            }

            //Remove Existing Zipfile.
            String zipfile = Path.Combine(Application.StartupPath, "bb_export.zip");
            if (File.Exists(zipfile))
            {
                File.Delete(zipfile);
            }

            //Create new Zipfile.
            using (ZipFile zip = new ZipFile())
            {
                zip.AddEntry(".bb-package-info", bbpackageinfo);
                zip.AddEntry(".bb-package-sig", bbpackagesig);

                zip.AddFile("imsmanifest.xml");
                foreach (String f in Directory.GetFiles(Application.StartupPath, "*.dat", SearchOption.TopDirectoryOnly))
                {
                    zip.AddFile(f, ""); //Ignore Path...
                }
                zip.Save(zipfile);
            }

            Console.WriteLine("Archive=" + zipfile);

            return zipfile;
        }

        /// <summary>
        /// Output Document.
        /// </summary>
        private static void Recurse(XmlNode root, XmlNode parent)
        {
            Debug.Print(";Recursing Children of {0} <{1}>", root.NodeType.ToString(), root.Name);
            Debug.Indent();

            foreach (XmlNode node in root.ChildNodes)
            {
                XmlNode newchild = null;

                if (!HasAttribute(node, "ExportedAs"))
                {
                    Debug.Print("Warning {0} attribute is missing for <{1}>", "ExportedAs", node.Name);
                }

                String ExportedAs = "ExportedAs_" + SafeGetAttribute(node, "ExportedAs", node.Name);

                // Get the desired method by name: DisplayName
                MethodInfo methodInfo = typeof(BlackbBoardExport).GetMethod(ExportedAs);

                if (methodInfo != null)
                {
                    if (SafeGetAttribute(node, "ExportedAs", node.Name) == node.Name)
                    {
                        Debug.Print("Calling fallback {0}() for <{1}>", methodInfo.Name, node.Name);
                    }
                    else
                    {
                        Debug.Print("Calling {0}() for <{1}>", methodInfo.Name, node.Name);
                    }

                    // Use the instance to call the method without arguments...

                    //a) Non Static Invoke...
                    //methodInfo.Invoke(this, null);

                    //b) Static Invoke...
                    object[] parameters = new object[2];
                    parameters[0] = node;
                    parameters[1] = parent;

                    newchild = (XmlNode)methodInfo.Invoke(null, parameters);
                }
                else
                {
                    Debug.Print("Warning: {0}() is missing for <{1}>", ExportedAs, node.Name);

                    methodInfo = typeof(BlackbBoardExport).GetMethod("ExportedAs_Fallback");
                    if (methodInfo != null)
                    {
                        Debug.Print("Warning: calling ExportedAs_Fallback() for <{1}>", ExportedAs, node.Name);

                        object[] parameters = new object[2];
                        parameters[0] = node;
                        parameters[1] = parent;

                        newchild = (XmlNode)methodInfo.Invoke(null, parameters);
                    }
                    else
                    {
                        Debug.WriteLine("Warning: ExportedAs_Fallback() is missing");
                    }
                }
                //Debug.WriteLine("Info: returned from previous call>");

                //Invoke Static Method
                /*
                Assembly assemblyInstance = Assembly.GetExecutingAssembly();
                Type thisClass = assemblyInstance.GetType("EwbDesigner.NET.BlackbBoardExport", false, true);
                methodInfo = thisClass.GetMethod(node.Name);
                string key = "";
                if (methodInfo != null)
                {
                    object result = methodInfo.Invoke(null, null);
                    key = result.ToString();
                }
                */

                if (node.HasChildNodes)
                {
                    Recurse(node, newchild);
                }
            }
            Debug.Unindent();
        }

        private static void ResetVariables(string model)
        {
            idcounter = 1;                 //Generates unique ID'key
            now = DateTime.Now;            //The current Time of Export.
            //section = 0;                   //The current Section.
            //stampguid = "xyyz";            //The stamp'key base guid.
            //opdracht = -1;               //The current Opdracht being processed.

            Model = model;
            BaseDirectory = Path.GetDirectoryName(Model);
            ModelName = Path.GetFileNameWithoutExtension(Model);

            xml.RemoveAll();

            bbids.Clear();

            //Course_Files = Path.Combine(BaseDirectory, "course_files");
        }

        #endregion Methods

        #region ExportAsHandlers

        //public static XmlNode ExportedAs_Fallback(XmlNode node, XmlNode parent)
        //{
        //    //TODO, hardcoded tekst attribute...

        //    return CreateTitleTextNode(node, SafeGetAttribute(node, "titel", "Error"), SafeGetAttribute(node, "tekst", "Error"), parent);
        //}

        public static XmlNode ExportedAs_EWB(XmlNode node, XmlNode parent)
        {
            String title = SafeGetAttribute(node, "titel", node.Name);
            String code = SafeGetAttribute(node, "code", node.Name);

            //Write CourseToc Node.
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00023" identifierref="res00023"><code>Tentaminering</code></item>
            //XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");
            XmlNode item = parent.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }
            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode(title));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resources = xml.DocumentElement.SelectSingleNode("/manifest/resources");
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = title;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "course/x-bb-coursetoc";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            bbids.Add(res, bbidentifier.ToString());

            //Write a *.dat file.
            //String content = String.Format(res_course, "_" + bbidentifier++, code, title);
            String content = String.Format(res_coursetoc, "_" + bbidentifier++, title);
            File.WriteAllText(fn, content);

            return item;
        }

        public static void ExportedAs_Tasks(XmlNode node, XmlNode parent)
        {
            Debug.WriteLine(";Ignoring Tasks Wrapper");
        }

        public static XmlNode ExportedAs_Task(XmlNode node, XmlNode parent)
        {
            return CreateTask(node, "titel", parent);
        }

        public static void ExportedAs_Jit(XmlNode node, XmlNode parent)
        {
            //TODO
            //CreateLabelNode(node, "titel", parent);
            //CreateFolderResource(node, "tekst", "tekst", parent);
        }

        /// <summary>
        /// Export a Casus.
        /// Creates a Html Resource.
        /// Status : Checked.
        /// </summary>
        /// <param name="node">The intermediate format Node to export</param>
        public static void ExportedAs_TextResource(XmlNode node, XmlNode parent)
        {
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            CreateTitleTextResource(node.Name, "titel", "tekst", indent, parent);
        }

        /// <summary>
        /// Export a Casus.
        /// Creates a Html Resource.
        /// Status : Checked.
        /// </summary>
        /// <param name="node">The intermediate format Node to export</param>
        public static void ExportedAs_Text(XmlNode node, XmlNode parent)
        {
            CreateTitleTextNode(node, "titel", "tekst", parent);
        }

        /// <summary>
        /// Export a Casus.
        /// Creates a Html Resource.
        /// Status : Checked.
        /// </summary>
        /// <param name="node">The intermediate format Node to export</param>
        public static void ExportedAs_Html(XmlNode node, XmlNode parent)
        {
            CreateTitleHtmlNode(node, "titel", "tekst", parent);
        }

        public static void ExportedAs_List(XmlNode node, XmlNode parent)
        {
            CreateList(node, "titel", "leerdoel", parent);
        }

        public static void ExportedAs_Resource(XmlNode node, XmlNode parent)
        {
            CreateFileResource(node, "titel", "bestand", parent);
        }

        public static void ExportedAs_Metadata(XmlNode node, XmlNode parent)
        {
            CreateMetaData(node, parent);
        }

        //public static void ExportedAs_Omschrijving(XmlNode node, XmlNode parent)
        //{
        //    CreateOmschrijving(node);
        //}

        public static void ExportedAs_Opdracht(XmlNode node, XmlNode parent)
        {
            CreateAssignment(node, "titel", "instructie", parent);
        }

        #endregion ExportAsHandlers

        #region Helpers

        private static Int64 Epoch(DateTime dt)
        {
            TimeSpan ts = (dt.ToUniversalTime() - new DateTime(1970, 1, 1));
            return (Int64)ts.TotalSeconds;
        }

        /// <summary>
        /// Converts a Boolean into a String.
        /// </summary>
        /// <param name="b">The value to convert</param>
        /// <returns>"true" or "false".</returns>
        private static String BoolToString(Boolean b)
        {
            if (b)
            {
                return "true";
            }
            else
            {
                return "false";
            }
        }

        /// <summary>
        /// Converts a Boolean into a Integer.
        /// </summary>
        /// <param name="b">The value to convert</param>
        /// <returns>"1" or "0".</returns>
        private static String BoolToInt(Boolean b)
        {
            if (b)
            {
                return "1";
            }
            else
            {
                return "0";
            }
        }

        /// <summary>
        /// Safely tries to retrieve an attribute value. If not found it returns the default value.
        /// </summary>
        /// <param name="node">The XmlNode to retrieve an attribute from</param>
        /// <param name="Name">The Name of the attribute</param>
        /// <param name="defaultvalue">The default value in case the attibute is missing</param>
        /// <returns>The attribute value or the defaultvalue</returns>
        private static String SafeGetAttribute(XmlNode node, String Name, String defaultvalue)
        {
            if (node.Attributes.GetNamedItem(Name) != null)
            {
                return node.Attributes.GetNamedItem(Name).Value;
            }
            else
            {
                Debug.Indent();
                Debug.Print("*** Attribute {0}.{1} is missing, using default value '{2}' ***", node.Name, Name, defaultvalue);
                Debug.Unindent();

                return defaultvalue;
            }
        }

        /// <summary>
        /// Checks if an attribute is present.
        /// </summary>
        /// <param name="node">The XmlNode to retrieve an attribute from</param>
        /// <param name="Name">The Name of the attribute</param>
        /// <returns>true if the attribute is present</returns>
        private static Boolean HasAttribute(XmlNode node, String Name)
        {
            return node.Attributes.GetNamedItem(Name) != null;
        }

        #endregion Helpers

        #region ExportAsHandlerHelpers

        /// <summary>
        /// Generate a List from a StringList.
        /// </summary>
        /// <param name="node">The node to process</param>
        /// <param name="titleatt">The name of the attribute holding the code</param>
        /// <param name="listattr">The name of the attribute holding the filename</param>
        private static XmlNode CreateList(XmlNode node, String titleattr, String listattr, XmlNode parent)
        {
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            String text = "";

            text += "<H2>" + SafeGetAttribute(node, titleattr, node.Name) + ":</H2><P />\r\n";

            text += "<UL title=\"" + SafeGetAttribute(node, titleattr, node.Name) + "\">";

            //Debugger.Break();

            String list = SafeGetAttribute(node, listattr, "");
            String[] sa = list.Split(ListSeparator);

            foreach (String value in sa)
            {
                text += String.Format("\r\n<LI>{0}</LI>", value);
            }
            text += "\r\n</UL>\r\n";

            return CreateTitleTextNode(node.Name, SafeGetAttribute(node, titleattr, node.Name), text, indent, parent);
        }

        /// <summary>
        /// Generate a Resource from a Filename.
        /// </summary>
        /// <param name="node">The node to process</param>
        /// <param name="titleatt">The name of the attribute holding the code</param>
        /// <param name="resattr">The name of the attribute holding the filename</param>
        private static XmlNode CreateFileResource(XmlNode node, String titleattr, String resattr, XmlNode parent)
        {
            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String text = SafeGetAttribute(node, resattr, "");
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            //TODO - Implement
            return CreateTitleTextNode(name, title, text, indent, parent);
        }

        private static XmlNode CreateFolderResource(XmlNode node, String titleattr, String textattr, XmlNode parent)
        {
            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String text = SafeGetAttribute(node, textattr, "");
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            //TODO - Implement
            return CreateTitleTextNode(name, title, text, indent, parent);
        }

        private static XmlNode CreateMetaData(XmlNode node, XmlNode parent)
        {
            String html = "<UL title=\"Metadata\">";
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            foreach (XmlAttribute attr in node.Attributes)
            {
                if (attr.Name != "ExportedAs")
                {
                    //TODO Extract into a method of it'key Own.
                    String v = attr.Value;
                    v = v.Replace("©", "&#x00A9;");

                    html += String.Format("\r\n<LI><STRONG>{0}</STRONG>: {1}</LI>", attr.Name, v);
                }
            }
            html += "\r\n</UL>\r\n";

            return CreateTitleTextNode("Metadata", "Metadata", html, indent, parent);
        }

        private static XmlNode CreateTitleTextNode(XmlNode node, String titleattr, String textattr, XmlNode parent)
        {
            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String text = SafeGetAttribute(node, textattr, "");
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            //TODO - Implement
            return CreateTitleTextNode(name, title, text, indent, parent);
        }

        /// <summary>
        /// Generate a CONTENT dat file, using res_text template.
        /// 
        /// This Method does the 'real' work!
        /// /// </summary>
        /// <param name="name"></param>
        /// <param name="title"></param>
        /// <param name="text"></param>
        /// <param name="indent"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private static XmlNode CreateTitleTextNode(String name, String title, String text, Int32 indent, XmlNode parent)
        {
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00023" identifierref="res00023"><code>Tentaminering</code></item>
            //XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");
            XmlNode item = parent.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }

            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode(title));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resources = xml.DocumentElement.SelectSingleNode("/manifest/resources");
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = title;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "resource/x-bb-document";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            //Write a *.dat file.
            String key = parent.Attributes.GetNamedItem("bb:identifierref").Value;
            String bbid = bbids[key];

            Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            bbids.Add(res, bbidentifier.ToString());

            String content = String.Format(res_text, "_" + bbidentifier++, title, text, "_" + bbid);
            File.WriteAllText(fn, content);

            return item;
        }

        private static XmlNode CreateLabelNode(XmlNode node, String titleattr, XmlNode parent)
        {
            Int32 aid = idcounter++;

            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            return CreateLabelNode(name, title, indent, parent);
        }

        /// <summary>
        /// Generate a COURSETOC dat file, using res_label template.
        /// 
        /// This Method does the 'real' work!
        /// </summary>
        /// <param name="name"></param>
        /// <param name="title"></param>
        /// <param name="indent"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private static XmlNode CreateLabelNode(String name, String title, Int32 indent, XmlNode parent)
        {
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00023" identifierref="res00023"><code>Tentaminering</code></item>
            //XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");
            XmlNode item = parent.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }

            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode(title));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resources = xml.DocumentElement.SelectSingleNode("/manifest/resources");
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = title;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "resource/x-bb-document";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            //Write a *.dat file.
            Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            bbids.Add(res, bbidentifier.ToString());

            String content = String.Format(res_label, "_" + bbidentifier++, title);
            File.WriteAllText(fn, content);

            return item;
        }

        private static XmlNode CreateFolderNode(XmlNode node, String titleattr, XmlNode parent)
        {
            Int32 aid = idcounter++;

            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            return CreateFolderNode(name, title, indent, parent);
        }

        /// <summary>
        /// Generate a COURSETOC dat file, using res_label template.
        /// 
        /// This Method does the 'real' work!
        /// </summary>
        /// <param name="name"></param>
        /// <param name="title"></param>
        /// <param name="indent"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private static XmlNode CreateFolderNode(String name, String title, Int32 indent, XmlNode parent)
        {
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00023" identifierref="res00023"><code>Tentaminering</code></item>
            //XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");
            XmlNode item = parent.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }

            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode(title));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resources = xml.DocumentElement.SelectSingleNode("/manifest/resources");
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = title;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "resource/x-bb-document";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            //Write a *.dat file.
            String key = parent.Attributes.GetNamedItem("bb:identifierref").Value;
            String bbid = bbids[key];

            Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            bbids.Add(res, bbidentifier.ToString());

            String content = String.Format(res_folder, "_" + bbidentifier++, title, "", "_" + bbid);

            File.WriteAllText(fn, content);

            return item;
        }

        /// <summary>
        /// Generate a CONTENT dat file, using res_test template.
        /// 
        /// This Method does the 'real' work!
        /// </summary>
        /// <param name="name"></param>
        /// <param name="title"></param>
        /// <param name="text"></param>
        /// <param name="indent"></param>
        /// <param name="parent"></param>
        /// <returns></returns>
        private static XmlNode CreateTitleTextResource(String name, String title, String text, Int32 indent, XmlNode parent)
        {
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00023" identifierref="res00023"><code>Tentaminering</code></item>
            //XmlNode organization = xml.DocumentElement.SelectSingleNode("/manifest/organizations/organization");
            XmlNode item = parent.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }

            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode(title));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resources = xml.DocumentElement.SelectSingleNode("/manifest/resources");
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = title;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "resource/x-bb-document";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            //Write a *.dat file.
            String key = parent.Attributes.GetNamedItem("bb:identifierref").Value;
            String bbid = bbids[key];

            Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            bbids.Add(res, bbidentifier.ToString());

            String content = String.Format(res_text, "_" + bbidentifier++, title, text, "_" + bbid);
            File.WriteAllText(fn, content);

            return item;
        }

        private static XmlNode CreateTitleHtmlNode(XmlNode node, String titleattr, String htmlattr, XmlNode parent)
        {
            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String html = SafeGetAttribute(node, htmlattr, htmlattr);
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            return CreateTitleHtmlNode(name, title, html, indent, parent);
        }

        private static XmlNode CreateTitleHtmlNode(String name, String title, String html, Int32 indent, XmlNode parent)
        {
            //TODO - Implement Better
            return CreateTitleTextNode(name, title, String.Format("<H2>{0}</H2>\r\n<P>{0}</P>\r\n", title, html), indent, parent);
        }

        private static XmlNode CreateTask(XmlNode node, String titleattr, XmlNode parent)
        {
            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String studielast = SafeGetAttribute(node, "studielast", SafeGetAttribute(node, "complexiteit", "0"));
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            if (!String.IsNullOrEmpty(studielast) && studielast != "0")
            {
                title += " (" + studielast + ")";
            }

            //TODO - Implement Better
            return CreateFolderNode(name, title, indent, parent);
        }

        private static XmlNode CreateOmschrijving(XmlNode node, XmlNode parent)
        {
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            //<xs:attribute xmlns:q1="pimv2" name="titel" type="xs:string" />
            //<xs:attribute xmlns:q1="pimv2" name="instructie" type="xs:string" />
            //<xs:attribute xmlns:q1="pimv2" name="type">
            //  <xs:simpleType>
            //    <xs:restriction base="xs:string">
            //      <xs:enumeration value="studeeropdracht" />
            //      <xs:enumeration value="aanvulopdracht" />
            //      <xs:enumeration value="luisteropdracht" />
            //      <xs:enumeration value="demonstratieopdracht" />
            //    </xs:restriction>
            //  </xs:simpleType>

            String title = SafeGetAttribute(node, "titel", node.Name);
            String instructie = SafeGetAttribute(node, "instructie", "instructie");
            String type = SafeGetAttribute(node, "type", "geen");

            String html = String.Format("<h2>Instructie voor {0}</h2>{1}", type, instructie);

            //TODO - Implement Better
            return CreateTitleTextNode(node.Name, title, html, indent, parent);
        }

        private static XmlNode CreateAssignment(XmlNode node, String titleattr, String textattr, XmlNode parent)
        {
            Int32 aid = idcounter++;

            String name = node.Name;
            String title = SafeGetAttribute(node, titleattr, node.Name);
            String text = SafeGetAttribute(node, textattr, "");
            Int32 indent = Int32.Parse(SafeGetAttribute(node, "indent", "0"));

            //TODO - Implement
            return CreateTitleTextNode(name, title, text, indent, parent);

            //////////////////////////////////////////////////////////////////////////////////
            ////(1)

            //CheckModule(DETAILS + "/MOD[NAME=\"assignment\"]", "assignment");

            //_appendMODtoSECTION(idcounter++, section, "assignment", aid, now, "0", indent, true, false);

            //////////////////////////////////////////////////////////////////////////////////
            ////(2)
            //_appendINSTANCEtoMODULE("assignment", aid, code, true, true);

            //////////////////////////////////////////////////////////////////////////////////
            ////(3)
            //XmlNode mod = _appendNP(MODULES, "MOD");
            //_appendNAT(mod, "ID", aid);
            //_appendNAT(mod, "MODTYPE", "assignment");
            //_appendNAT(mod, "NAME", code);
            //_appendNAT(mod, "DESCRIPTION", title);
            //_appendNAT(mod, "FORMAT", "1");
            //_appendNAT(mod, "RESUBMIT", "0");
            //_appendNAT(mod, "PREVENTLATE", "0");
            //_appendNAT(mod, "EMAILTEACHERS", "0");
            //_appendNAT(mod, "TYPE", "0");                                               //Online, offline??
            //_appendNAT(mod, "VAR1", "0");
            //_appendNAT(mod, "VAR2", "0");
            //_appendNAT(mod, "VAR3", "0");
            //_appendNAT(mod, "VAR4", "0");
            //_appendNAT(mod, "VAR5", "0");
            //_appendNAT(mod, "ASSIGNMENTTYPE", "offline");                               //TODO
            //_appendNAT(mod, "MAXBYTES", "2097152");
            //_appendNAT(mod, "TIMEDUE", "0");
            //_appendNAT(mod, "TIMEAVAILABLE", "0");
            //_appendNAT(mod, "GRADE", "0");
            //_appendNAT(mod, "TIMEMODIFIED", now);
            //_appendN(mod, "SUBMISSIONS");

            /*
             * NOT IMPLEMENTED YET, Fixed to Offline
            //4
            fn =""+ obj.Value(mini, "DATA", "Model");
            if (fn != "") {
              dbg.DebugPrint("Processing Model: "+fn);

              var fso = new ActiveXObject("Scripting.FileSystemObject");
              var fldr;
              var tf;

              //Beware current Directory is used. Better set by program.
              if (!fso.FolderExists(target + "moddata")) {
                fldr = fso.CreateFolder(target + "moddata");
              }

              if (!fso.FolderExists(target + "moddata\\assignment")) {
                fldr = fso.CreateFolder(target + "moddata\\assignment");
              }

              if (!fso.FolderExists(target + "moddata\\assignment\\" + aid)) {
                fldr = fso.CreateFolder(target + "moddata\\assignment\\" + aid);
              }

              if (!fso.FolderExists(target + "moddata\\assignment\\" + aid + "\\responses")) {
                fldr = fso.CreateFolder(target + "moddata\\assignment\\" + aid + "\\responses");
              }
                
              fso.CopyFile(fn, target + "moddata\\assignment\\" + aid + "\\responses\\");
            } else {
              dbg.DebugPrint(obj.TypeName);
              dbg.DebugPrint(obj.Cell(ini, "ModelGuidCol", i));
              dbg.DebugPrint(mrvf);

              var xml = obj.Xml(mrvf, obj.TypeName);

              if (xml!="") {
                ////////////////////////////////////////////////////////////////////////////////
                //Write physical file
                ////////////////////////////////////////////////////////////////////////////////
                var fso = new ActiveXObject("Scripting.FileSystemObject");
                var fldr;
                var tf;

                //Beware current Directory is used. Better set by program.
                if (!fso.FolderExists(target + "moddata")) {
                  fldr = fso.CreateFolder(target + "moddata");
                }

                if (!fso.FolderExists(target + "moddata\\assignment")) {
                  fldr = fso.CreateFolder(target + "moddata\\assignment");
                }

                if (!fso.FolderExists(target + "moddata\\assignment\\" + aid)) {
                  fldr = fso.CreateFolder(target + "moddata\\assignment\\" + aid);
                }

                if (!fso.FolderExists(target + "moddata\\assignment\\" + aid + "\\responses")) {
                  fldr = fso.CreateFolder(target + "moddata\\assignment\\" + aid + "\\responses");
                }

                //Emit Single File to a Directory.
                tf = fso.CreateTextFile(target + "moddata\\assignment\\" + aid + "\\responses\\Antwoordmodel.htm", true, false); //was .xhtml
                tf.Write(xml);
                tf.Close();

                dbg.DebugPrint("Generated Model Answer as "" + target + "moddata\\assignment\\" + aid + "\\responses\\Antwoordmodel.htm" + """);

                //Move Embedded Files...
                try {
                  fso.MoveFile(target + "temp\\" + filename + "_*.*", target + "course_files\\resources_" + cid);
                } catch(exception) {
                //Silence
                }
              } else {
                dbg.DebugPrint("Empty Answer Model (no file or title).");
              }
            }
          }
        }
        */
        }

        private static void CreateBlackBoardHeader()
        {
            ////////////////////////////////////////////////////////////////////////////////
            //Create Basic Structure.
            ////////////////////////////////////////////////////////////////////////////////

            //<?xml version="1.0" encoding="UTF-8" ?> 
            XmlDeclaration xmlDeclaration = xml.CreateXmlDeclaration("1.0", "utf-8", null);

            //<manifest identifier="man00001" xmlns:bb="http://www.blackboard.com/content-packaging/">
            XmlElement root = xml.CreateElement("bb:manifest");
            root.SetAttribute("identifier", "man00001");
            root.SetAttribute("xmlns:bb", bbnsURI);
            xml.AppendChild(root);

            //<organizations default="toc00001">
            XmlNode orgs = root.AppendChild(xml.CreateElement("organizations"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:default");
                att.Value = "toc00001";
                orgs.Attributes.Append(att);
            }

            //<organization identifier="toc00001">
            XmlNode org = orgs.AppendChild(xml.CreateElement("organization"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = "toc00001";
                org.Attributes.Append(att);
            }


            //TODO MOVE FOLLOWING CODE TO ExportAs_EWB (so we know the title)..
            Int32 cnt = idcounter++;

            String itm = String.Format("itm{0:00000}", cnt);
            String res = String.Format("res{0:00000}", cnt);
            String fn = Path.ChangeExtension(res, ".dat");

            //1) Create <item>

            //<item identifier="itm00001" identifierref="res00004">
            //<code>COURSE_DEFAULT.Announcements.APPLICATION.label</code> 
            //</item>
            XmlNode item = org.AppendChild(xml.CreateElement("item"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = itm;
                item.Attributes.Append(att);
            }
            {
                XmlAttribute att = xml.CreateAttribute("bb:identifierref");
                att.Value = res;
                item.Attributes.Append(att);
            }

            //2) Create <item>/<code>

            XmlNode titlenode = item.AppendChild(xml.CreateElement("title"));
            titlenode.AppendChild(xml.CreateTextNode("COURSE_DEFAULT.Announcements.APPLICATION.label"));
            item.AppendChild(titlenode);

            //3) Create <resource>

            //<resources>
            XmlNode resources = root.AppendChild(xml.CreateElement("resources"));

            //<resource bb:file="res00025.dat" bb:code="Antwoordmodel &amp; cesuur" identifier="res00025" type="resource/x-bb-document" xml:base="res00025"/>
            XmlNode resource = resources.AppendChild(xml.CreateElement("resource"));
            {
                XmlAttribute att = xml.CreateAttribute("bb:file");
                att.Value = fn;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:title");
                att.Value = ModelName;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:identifier");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("bb:type");
                att.Value = "course/x-bb-coursesetting";
                resource.Attributes.Append(att);
            }

            {
                XmlAttribute att = xml.CreateAttribute("xml:base");
                att.Value = res;
                resource.Attributes.Append(att);
            }

            //4) Write content to file.

            //Write a *.dat file.
            //String key = parent.Attributes.GetNamedItem("bb:identifierref").Value;
            //String bbid = bbids[key];

            //Debug.WriteLine(String.Format("{0}={1}", res, bbidentifier));
            //bbids.Add(res, bbidentifier.ToString());

            String content = String.Format(res_course, "_" + bbidentifier++, ModelName, ModelName);
            File.WriteAllText(fn, content);
        }

        private static void CreateBlackBoardFooter()
        {
            //TODO - Implement
        }

        #endregion ExportAsHandlerHelpers
    }
}
